#ifndef __PARSER
#define __PARSER

#include<cstdio>
#include<cstdlib>
#include<string>
#include<set>
#include<iostream>
#include<sstream>
#include"io.h"
#include"hasher.h"
#include"logger.h"




namespace Parser
{
	using std::set;
	using std::string;
	using std::istringstream;
	using namespace Stream;
	using namespace Hashing;
	using namespace Custom;
	typedef pair<unsigned char,unsigned char> utf8;

	enum TokenType
	{
		Bad = 0,
		Text = 1,
		Title = 2
	};


	inline char tolower(char c)
	{
		if (c>='A' && c<='Z')
			return c-'A'+'a';
		return c;
	}

	inline string trim(string s,char c)
	{
		int start=0,end = s.length()-1;
		while (s[start]>0 && s[start]==c)
			++start;
		while (end>=start && s[end]>0 && s[end]==c)
			--end;
		++end;
		if (end==start)
			return "";
		if (start!=0)
			for (int i=0;start+i<end;++i)
				s[i]=s[start+i];
		s.resize(end-start);
		return s;
	}

	inline vector<string> split(const string &s,char c)
	{
		vector<string> result;
		int lastpos=0;
		while (lastpos < s.length())
		{
			int i=lastpos;
			while (i<s.length() && s[i]!=c)
				++i;
			if (lastpos<i)
				result.push_back(s.substr(lastpos,i-lastpos));
			lastpos = i+1;
		}
		return result;
	}






	class parser
	{
		FILE *file;
		bool stemming;
		char buffer[3000];
		string filename;
		string basePath;
		ll hash;
		FastIstream *in;
		char tokenBuffer[3000];
		char *tokenPtr;
		TokenType actToken;
		istringstream *stringStream;
		Logger logger;


		map<utf8,char> polskie;
		map<utf8,utf8> utf8lower;
		map<ll,string> morfologik;
		void initReading()
		{
			if ((file = fopen((basePath+filename).c_str(),"rb")) == 0)
				throw exception((string("nie mozna otworzyc pliku: ")+(basePath+filename).c_str()).c_str());
			in = new FastIstream(file);
			*in >> buffer;
		}

		void loadMorph()
		{
			try
			{
				logger.Log(LogLevel::DebugInfo,"morfologik start");
				FastIstream morfo(fopen((basePath+"morfologik.txt").c_str(),"r"));
				int line=0;
				while (morfo.getLine(buffer))
				{
					int len = strlen(buffer);
					int pos=len;
					--pos;
					while (pos>=0 && buffer[pos]==' ')
						buffer[pos--]=0;
					if (pos<0)
						continue;
					while (pos>=0 && buffer[pos]!=' ')
						--pos;
					++pos;
					string toWord(buffer+pos);
					while (pos>=0)
					{
						--pos;
						while (pos>=0 && buffer[pos]==' ')
							buffer[pos--]=0;
						if (pos<0)
							break;
						while (pos>=0 && buffer[pos]!=' ')
							--pos;
						++pos;
						if (pos==0)
							morfologik[hasher::value(buffer+pos,hash)] = toWord;
						if (pos<=0)
							break;
					}
					++line;
				}
				logger.Log(LogLevel::DebugInfo,"morfologik koniec");
			}
			catch(...)
			{
				logger.Log(LogLevel::DebugInfo,"brak morfologika");
			}
		}
	public:
		parser(const string &basePath,const char *filename,bool stemming=0,bool morf=true,ll hash=1111111111185LL) : stemming(stemming) , in(0) ,file(0), hash(hash),
			filename(filename) , basePath(basePath) , tokenPtr(tokenBuffer) , stringStream(0) , logger(stderr,LogLevel::Debug)
		{
			logger.AddLog(basePath+"logs\\"+Logger::DefaultLogName(),LogLevel::Info);
			logger.Log(LogLevel::DebugInfo,"parser konstruktor");
			tokenBuffer[0]=0;
			polskie[utf8(196,132)]='A';
			polskie[utf8(196,133)]='a';
			polskie[utf8(196,134)]='C';
			polskie[utf8(196,135)]='c';
			polskie[utf8(196,152)]='E';
			polskie[utf8(196,153)]='e';
			polskie[utf8(197,129)]='L';
			polskie[utf8(197,130)]='l';
			polskie[utf8(197,131)]='N';
			polskie[utf8(197,132)]='n';
			polskie[utf8(197,154)]='S';
			polskie[utf8(197,155)]='s';
			polskie[utf8(197,185)]='Z';
			polskie[utf8(197,186)]='z';
			polskie[utf8(197,187)]='Z';
			polskie[utf8(197,188)]='z';
			polskie[utf8(195,147)]='O';
			polskie[utf8(195,179)]='o';
			utf8lower[utf8(196,132)] = utf8(196,133);
			utf8lower[utf8(196,134)] = utf8(196,135);
			utf8lower[utf8(196,152)] = utf8(196,153);
			utf8lower[utf8(197,129)] = utf8(197,130);
			utf8lower[utf8(197,131)] = utf8(197,132);
			utf8lower[utf8(197,154)] = utf8(197,155);
			utf8lower[utf8(197,185)] = utf8(197,186);
			utf8lower[utf8(197,187)] = utf8(197,188);
			utf8lower[utf8(195,147)] = utf8(195,179);
			if (morf)
				loadMorph();
		}
		~parser()
		{
			if (in)
				delete in;
			if (stringStream)
				delete stringStream;
		}


		//etap 1
		inline bool toLower(char *s,bool ignoreOther=true)
		{
			while (*s)
			{
				if (*s<0)
				{
					utf8 act = utf8(*s,*(s+1));
					if (utf8lower.find(act) == utf8lower.end())
					{
						if (polskie.find(act) == polskie.end() && ignoreOther)
							return false;
						else 
							goto next;
					}
					utf8 c = utf8lower[act];
					*s = c.first;
					*(s+1) = c.second;
					next:;
					++s;
				}
				else
					*s = tolower(*s);
				++s;
			}
			return true;
		}
		
		//czyszczenie innych znakow z konca i poczatku
		//etap 2
		inline bool normalize(char *s)
		{
			int start=0,end=strlen(s)-1;
			while (s[start]>0 && !isalnum(s[start]))
				++start;
			while (end>=start && s[end]>0 && !isalnum(s[end]))
				--end;
			++end;
			if (end==start)
				return false;
			if (start!=0)
				for (int i=0;start+i<end;++i)
					s[i]=s[start+i];
			s[end-start]=0;
			return true;
		}

		//etap 3
		inline bool morph(char *s)
		{
			ll v = hasher::value(s,hash);
			map<ll,string>::iterator it = morfologik.find(v);
			if (it != morfologik.end())
			{
				const char *ns = it->second.c_str();
				for (int i=0;ns[i];++i)
					*(s++) = ns[i];
				*s=0;
			}
			return true;
		}

		//etap 4
		inline bool toASCI(char *s)
		{
			char *cur = s;
			while (*cur)
			{
				if (*cur<0)
				{
					utf8 act = utf8(*cur,*(cur+1));
					if (polskie.find(act) == polskie.end())
						return false;
					*s = polskie[act];
					++cur;
				}
				else
					*s = tolower(*cur);
				++cur;
				++s;
			}
			*s = 0;
			return true;
		}
		
		//etap 5 (opcjonalnie)
		inline bool stemm(char *s)
		{
			return true;
		}



		inline bool getTitle(char *title)
		{
			if (!file)
				initReading();
			if (!(*in))
				return false;
			(*in).getLine(title);
			tokenPtr = tokenBuffer;
			strcpy(tokenBuffer,title);
			actToken = TokenType::Title;
			return true;
		}

		inline bool tokenize(char *token)
		{
			bool valid = 1;
			valid &= toLower(token);
			if (valid)
				valid &= normalize(token);
			if (valid)
				valid &= morph(token);
			if (valid)
				valid &= toASCI(token);
			if (stemming && valid)
				valid &= stemm(token);
			if (!valid)
				*token = 0;
			return valid;
		}

		inline void InitStream(const string &s)
		{
			if (stringStream)
				delete stringStream;
			stringStream = new istringstream(s);
			tokenPtr = tokenBuffer;
			tokenBuffer[0] = 0;
		}

		inline bool GetTitleFromStream(char *title)
		{
			if (!(*stringStream))
				return false;
			stringStream->getline(title,3000);
			tokenPtr = tokenBuffer;
			strcpy(tokenBuffer,title);
			actToken = TokenType::Title;
			return true;
		}

		inline TokenType GetTokenFromStream(char *token,char *fulltoken)
		{
			if (!(*stringStream))
				return TokenType::Bad;
			while (*tokenPtr > 0 && !isalnum(*tokenPtr) && *tokenPtr!='#')
			{
				*(fulltoken++) = *tokenPtr;
				++tokenPtr;
			}
			while (*tokenPtr == 0)
			{
				actToken = TokenType::Text;
				if (!(*stringStream >> tokenBuffer))
					return TokenType::Bad;
				tokenPtr = tokenBuffer;
				while (*tokenPtr > 0 && !isalnum(*tokenPtr) && *tokenPtr != '#')
				{
					*(fulltoken++) = *tokenPtr;
					++tokenPtr;
				}
			}
			int len = 0;
			while (*tokenPtr!=0 && (isalnum(*tokenPtr) || *tokenPtr == '#' || *tokenPtr < 0))
			{
				*(fulltoken++) = *tokenPtr;
				token[len++] = *tokenPtr;
				++tokenPtr;
			}
			while (*tokenPtr>0 && !isalnum(*tokenPtr) && !isspace(*tokenPtr))
			{
				*(fulltoken++) = *tokenPtr;
				++tokenPtr;
			}
			*fulltoken = 0;
			token[len] = 0;
			if (strcmp(token,"##TITLE##")==0)
				return TokenType::Bad;
			tokenize(token);
			return actToken;
		}


		inline TokenType getToken(char *token)
		{
			if (!file)
				initReading();
			if (!(*in))
				return TokenType::Bad;

			while (*tokenPtr > 0 && !isalnum(*tokenPtr) && *tokenPtr!='#')
				++tokenPtr;
			while (*tokenPtr == 0)
			{
				actToken = TokenType::Text;
				if (!(*in >> tokenBuffer))
					return TokenType::Bad;
				tokenPtr = tokenBuffer;
				while (*tokenPtr > 0 && !isalnum(*tokenPtr) && *tokenPtr != '#')
					++tokenPtr;
			}
			int len = 0;
			while (*tokenPtr!=0 && (isalnum(*tokenPtr) || *tokenPtr == '#' || *tokenPtr < 0))
			{
				token[len++] = *tokenPtr;
				++tokenPtr;
			}
			token[len] = 0;
			if (strcmp(token,"##TITLE##")==0)
				return TokenType::Bad;
			tokenize(token);
			return actToken;
		}
		inline unsigned int getPosition() 
		{
			if (!file)
				initReading();
			return (*in).ftell();
		}
	};


}


#endif